﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Skugo.Shared;
using System.Diagnostics;

namespace Skugo.Core
{
	// Responsible for receiving chunk data, packetizing them,
	// and distributing them to the corresponding plugins
	public class PacketRouter
	{
		// Store a buffer of all the data we've received thus far
        private List<Byte> Data = new List<Byte>();

		// Store the current packet on us
		PacketHeader CurrentPacket = new PacketHeader();

        // Save the project info away
        ProjectClient Client;

		// Constructor
		public PacketRouter(ProjectClient client)
		{
			// Register for an event so we know when the client receives data
            client.OnReceivedData += this.ReceivedData;

            // Store the project object away
            this.Client = client;

			// Initialize the data size
			this.CurrentPacket.DataSize = PacketHeader.InvalidDataSize;
		}

		// Occurs when we receive data
        private void ReceivedData(Client client, Byte[] incomingData)
		{
			// Add the data to our block
			this.Data.AddRange(incomingData);

			// Should we continue reading packets in? (for the below loop)
			Boolean contiueParsingPackets = false;

			do
			{
				// Get the array data
                Byte[] data = this.Data.ToArray();

				// Assume we have not read any packets in yet
				contiueParsingPackets = false;

				// If we're currently not working on a packet and we
				// at least have enough data for to read a header size...
                if (this.CurrentPacket.DataSize == PacketHeader.InvalidDataSize && this.Data.Count > PacketHeader.Size)
				{
					// Create a binary reader to parse the header data
                    BinaryReader headerReader = data.ToBinaryReader();

					// Read packet header...
                    this.CurrentPacket.DataSize = headerReader.ReadUInt32();
                    this.CurrentPacket.Type = (PacketType)headerReader.ReadByte();
                    this.CurrentPacket.InstanceGuid = headerReader.ReadInt64();
				}

				// Are we currently working on a packet, and do we have enough data to make a full packet?
                Boolean fullPacket = this.CurrentPacket.DataSize != PacketHeader.InvalidDataSize &&
                this.Data.Count >= (this.CurrentPacket.DataSize + PacketHeader.Size);


				// Check if we are now reading a packet and if we have enough data to read the packet
				if (fullPacket)
				{
					// Copy the data into a newly created packet buffer
                    Byte[] newData = new Byte[this.CurrentPacket.DataSize];
                    Buffer.BlockCopy(data, (Int32)PacketHeader.Size, newData, 0, (Int32)this.CurrentPacket.DataSize);

					// Based on the type of the packet...
                    switch (this.CurrentPacket.Type)
					{
                        // Are we getting the packet that tells us all the initialization data?
                        case PacketType.Initialize:
                        {
							// Create a binary reader to parse the create packet data (if we need it)
                            BinaryReader incoming = newData.ToBinaryReader();

							// Read in the project name
                            var projectName = incoming.ReadAsciiString();
                            this.Client.Setup(projectName);
                            break;
                        }

						// If the packet is a simple message to a plugin...
						case PacketType.Message:
						{
							// Find the corresponding local plugin and route the data to it
							Plugin plugin = this.Client.Plugins.GetPlugin(this.CurrentPacket.InstanceGuid);

							// Route the packet to the local plugin
                            plugin.Receive(newData, this.CurrentPacket.Type);
							break;
						}

						// If the packet is a simple message to a plugin...
						case PacketType.Create:
						{
							// Create a binary reader to parse the create packet data (if we need it)
                            BinaryReader incoming = newData.ToBinaryReader();

							// Read in the type name
							String typeName = incoming.ReadAsciiString();

							// Create the plugin
                            PluginFactory.CreatePlugin(typeName, this.CurrentPacket.InstanceGuid, this.Client, incoming);
							break;
						}

						// If the packet is a simple message to a plugin...
						case PacketType.Destroy:
						{
							// Tell the factory to destroy the plugin
                            this.Client.Plugins.Destroy(this.CurrentPacket.InstanceGuid);
							break;
						}
					}

					// Clear all this packet's data from the buffer
                    this.Data.RemoveRange(0, (Int32)(PacketHeader.Size + this.CurrentPacket.DataSize));

					// Reset the current packet size
                    this.CurrentPacket.DataSize = PacketHeader.InvalidDataSize;

					// We read a packet, we should attempt to continue!
					contiueParsingPackets = true;
				}
			}
			// Loop until we stopped parsing packets
			while (contiueParsingPackets);
		}
	}
}
